﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework;
using System.Reflection;

namespace XNAViewer
{
    /// <summary>
    /// Helper for reading input from keyboard, gamepad, and touch input. This class 
    /// tracks both the current and previous state of the input devices, and implements 
    /// query methods for high level input actions such as "move up through the menu"
    /// or "pause the game".
    /// </summary>
    /// 
    public interface IKeyboardCallback
    {
        void KeyboardCallback(Keys key, bool released, ref KeyboardState newState, ref KeyboardState oldState);
    }

    public class InputState
    {
        #region Fields

        public const int MaxInputs = 4;

        public readonly KeyboardState[] CurrentKeyboardStates;

        public readonly KeyboardState[] LastKeyboardStates;

        public MouseState CurrentMouseState;
        public MouseState LastMouseState;

        public PlayerIndex ControllingPlayer;


        #endregion

        #region Initialization


        /// <summary>
        /// Constructs a new input state.
        /// </summary>
        public InputState()
        {
            CurrentKeyboardStates = new KeyboardState[MaxInputs];

            LastKeyboardStates = new KeyboardState[MaxInputs];

            ControllingPlayer = PlayerIndex.One;
        }


        #endregion

        #region Public Methods


        /// <summary>
        /// Reads the latest state of the keyboard and gamepad.
        /// </summary>
        public void Update()
        {
            for (int i = 0; i < MaxInputs; i++)
            {
                LastKeyboardStates[i] = CurrentKeyboardStates[i];

                CurrentKeyboardStates[i] = Keyboard.GetState((PlayerIndex)i);

            }

            LastMouseState = CurrentMouseState;
            CurrentMouseState = Mouse.GetState();
        }

        //----------------------------------------------------------------------------------------------
        // workaround from justastro at : http://forums.create.msdn.com/forums/p/1610/157478.aspx

        public static Enum[] GetEnumValues(Type enumType)
        {

            if (enumType.BaseType == typeof(Enum))
            {
                FieldInfo[] info = enumType.GetFields(BindingFlags.Static | BindingFlags.Public);
                Enum[] values = new Enum[info.Length];
                for (int i = 0; i < values.Length; ++i)
                {
                    values[i] = (Enum)info[i].GetValue(null);
                }
                return values;
            }
            else
            {
                throw new Exception("Given type is not an Enum type");
            }
        }


        static Enum[] keysEnumValues = GetEnumValues(typeof(Keys));

        public void GenerateKeyEvents(int playerIndex, IKeyboardCallback callback)
        {
            foreach (Keys key in keysEnumValues)
            {
                bool released = WasReleased(ref LastKeyboardStates[playerIndex], ref CurrentKeyboardStates[playerIndex], key);
                if (released || IsHeldKey(ref CurrentKeyboardStates[playerIndex], key))
                {
                    callback.KeyboardCallback(key, released, ref CurrentKeyboardStates[playerIndex], ref LastKeyboardStates[playerIndex]);
                }
            }
        }

        //----------------------------------------------------------------------------------------------
        // This is a way of generating 'pressed' events for keys that we want to hold down
        private bool IsHeldKey(ref KeyboardState current, Keys key)
        {
            return (current.IsKeyDown(key) && ((key == Keys.Left || key == Keys.Right || key == Keys.Up ||
                key == Keys.Down || key == Keys.PageUp || key == Keys.PageDown || key == Keys.A ||
                key == Keys.W || key == Keys.S || key == Keys.D || key == Keys.Q || key == Keys.Z)));
        }
        //----------------------------------------------------------------------------------------------

        private bool WasReleased(ref KeyboardState old, ref KeyboardState current, Keys key)
        {
            // figure out if the key was released between states.
            return old.IsKeyDown(key) && !current.IsKeyDown(key);
        }




        /// <summary>
        /// Helper for checking if a key was newly pressed during this update. The
        /// controllingPlayer parameter specifies which player to read input for.
        /// If this is null, it will accept input from any player. When a keypress
        /// is detected, the output playerIndex reports which player pressed it.
        /// </summary>
        public bool IsNewKeyPress(Keys key, PlayerIndex? controllingPlayer,
                                            out PlayerIndex playerIndex)
        {
            if (controllingPlayer.HasValue)
            {
                // Read input from the specified player.
                playerIndex = controllingPlayer.Value;

                int i = (int)playerIndex;

                return (CurrentKeyboardStates[i].IsKeyDown(key) &&
                        LastKeyboardStates[i].IsKeyUp(key));
            }
            else
            {
                // Accept input from any player.
                return (IsNewKeyPress(key, PlayerIndex.One, out playerIndex) ||
                        IsNewKeyPress(key, PlayerIndex.Two, out playerIndex) ||
                        IsNewKeyPress(key, PlayerIndex.Three, out playerIndex) ||
                        IsNewKeyPress(key, PlayerIndex.Four, out playerIndex));
            }
        }


        /// <summary>
        /// Helper for checking if a button was newly pressed during this update.
        /// The controllingPlayer parameter specifies which player to read input for.
        /// If this is null, it will accept input from any player. When a button press
        /// is detected, the output playerIndex reports which player pressed it.
        /// </summary>
        //public bool IsNewButtonPress(Buttons button)
        //{
        //    PlayerIndex outIndex;
        //    return IsNewButtonPress(button, ControllingPlayer, out outIndex);
        //}

        //public bool IsNewButtonPress(Buttons button, PlayerIndex? controllingPlayer,
        //                                             out PlayerIndex playerIndex)
        //{
        //    if (controllingPlayer.HasValue)
        //    {
        //        // Read input from the specified player.
        //        playerIndex = controllingPlayer.Value;

        //        int i = (int)playerIndex;

        //        return (CurrentGamePadStates[i].IsButtonDown(button) &&
        //                LastGamePadStates[i].IsButtonUp(button));
        //    }
        //    else
        //    {
        //        // Accept input from any player.
        //        return (IsNewButtonPress(button, PlayerIndex.One, out playerIndex) ||
        //                IsNewButtonPress(button, PlayerIndex.Two, out playerIndex) ||
        //                IsNewButtonPress(button, PlayerIndex.Three, out playerIndex) ||
        //                IsNewButtonPress(button, PlayerIndex.Four, out playerIndex));
        //    }
        //}


        /// <summary>
        /// Checks for a "menu select" input action.
        /// The controllingPlayer parameter specifies which player to read input for.
        /// If this is null, it will accept input from any player. When the action
        /// is detected, the output playerIndex reports which player pressed it.
        /// </summary>
        //public bool IsMenuSelect(PlayerIndex? controllingPlayer,
        //                         out PlayerIndex playerIndex)
        //{
        //    return IsNewKeyPress(Keys.Space, controllingPlayer, out playerIndex) ||
        //           IsNewKeyPress(Keys.Enter, controllingPlayer, out playerIndex) ||
        //           IsNewButtonPress(Buttons.A, controllingPlayer, out playerIndex) ||
        //           IsNewButtonPress(Buttons.Start, controllingPlayer, out playerIndex);
        //}


        ///// <summary>
        ///// Checks for a "menu cancel" input action.
        ///// The controllingPlayer parameter specifies which player to read input for.
        ///// If this is null, it will accept input from any player. When the action
        ///// is detected, the output playerIndex reports which player pressed it.
        ///// </summary>
        //public bool IsMenuCancel(PlayerIndex? controllingPlayer,
        //                         out PlayerIndex playerIndex)
        //{
        //    return IsNewKeyPress(Keys.Escape, controllingPlayer, out playerIndex) ||
        //           IsNewButtonPress(Buttons.B, controllingPlayer, out playerIndex) ||
        //           IsNewButtonPress(Buttons.Back, controllingPlayer, out playerIndex);
        //}


        /// <summary>
        /// Checks for a "menu up" input action.
        /// The controllingPlayer parameter specifies which player to read
        /// input for. If this is null, it will accept input from any player.
        /// </summary>
        //public bool IsMenuUp(PlayerIndex? controllingPlayer)
        //{
        //    PlayerIndex playerIndex;

        //    return IsNewKeyPress(Keys.Up, controllingPlayer, out playerIndex) ||
        //           IsNewButtonPress(Buttons.DPadUp, controllingPlayer, out playerIndex) ||
        //           IsNewButtonPress(Buttons.LeftThumbstickUp, controllingPlayer, out playerIndex);
        //}


        ///// <summary>
        ///// Checks for a "menu down" input action.
        ///// The controllingPlayer parameter specifies which player to read
        ///// input for. If this is null, it will accept input from any player.
        ///// </summary>
        //public bool IsMenuDown(PlayerIndex? controllingPlayer)
        //{
        //    PlayerIndex playerIndex;

        //    return IsNewKeyPress(Keys.Down, controllingPlayer, out playerIndex) ||
        //           IsNewButtonPress(Buttons.DPadDown, controllingPlayer, out playerIndex) ||
        //           IsNewButtonPress(Buttons.LeftThumbstickDown, controllingPlayer, out playerIndex);
        //}


        ///// <summary>
        ///// Checks for a "pause the game" input action.
        ///// The controllingPlayer parameter specifies which player to read
        ///// input for. If this is null, it will accept input from any player.
        ///// </summary>
        //public bool IsPauseGame(PlayerIndex? controllingPlayer)
        //{
        //    PlayerIndex playerIndex;

        //    return IsNewKeyPress(Keys.Escape, controllingPlayer, out playerIndex) ||
        //           IsNewButtonPress(Buttons.Back, controllingPlayer, out playerIndex) ||
        //           IsNewButtonPress(Buttons.Start, controllingPlayer, out playerIndex);
        //}



        //public GamePadState CurrentControllingPadState()
        //{
        //    return CurrentGamePadStates[(int)ControllingPlayer];
        //}


        #endregion
    }
}
